﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Microsoft.Samples
{
    [TemplatePart(Name = Webcam._onButton, Type = typeof(Button))]
    [TemplatePart(Name = Webcam._offButton, Type = typeof(Button))]
    [TemplatePart(Name = Webcam._targetRectangleName, Type = typeof(Rectangle))]
    [TemplatePart(Name = Webcam._recordingFrameName, Type = typeof(Grid))]
    [TemplatePart(Name = Webcam._devicesName, Type = typeof(ComboBox))]
    [TemplateVisualState(GroupName = Webcam.CommonStates, Name = Webcam.RecordingStateName)]
    [TemplateVisualState(GroupName = Webcam.CommonStates, Name = Webcam.NotRecordingStateName)]
    [TemplateVisualState(GroupName = Webcam.DevicesStates, Name = Webcam.OneDevice)]
    [TemplateVisualState(GroupName = Webcam.DevicesStates, Name = Webcam.MultipleDevices)]
    public class Webcam : Control, INotifyPropertyChanged
    {
        private const string _onButton = "_cameraOn";
        private const string _offButton = "_cameraOff";
        private const string _targetRectangleName = "_targetRectangle";
        private const string _recordingFrameName = "_recordingFrame";
        private const string _devicesName = "_devices";
        
        private const string CommonStates = "CommonStates";
        private const string RecordingStateName = "Recording";
        private const string NotRecordingStateName = "NotRecording";
        
        private const string DevicesStates = "DevicesStates";
        private const string OneDevice = "OneDevice";
        private const string MultipleDevices = "MultipleDevices";

        Button _on;
        Button _off;
        Rectangle _targetRectangle;
        Grid _recordingFrame;
        ComboBox _devices;

        VideoCaptureDevice _videoCaptureDevice;
        private CaptureSource _captureSource;
        public CaptureSource CaptureSource
        {
            get { return _captureSource; }
            set 
            {
                _captureSource = value; 
                OnPropertyChanged("CaptureSource"); 
            }
        }

        WriteableBitmap _bitmap;
        VideoBrush _videoBrush;
        ReadOnlyCollection<VideoCaptureDevice> _availableDevices;
        
        public Webcam()
        {
            this.DefaultStyleKey = typeof(Webcam);

            this.Loaded += (s, e) =>
                {
                    if (DesignerProperties.IsInDesignTool)
                    {
                        return;
                    }

                    // Not Recording State
                    VisualStateManager.GoToState(this, NotRecordingStateName, false);
                };
        }
        
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _on = GetTemplateChild(_onButton) as Button;
            _on.Click += (s, e) =>
                {
                    StartCamera();
                };

            _off = GetTemplateChild(_offButton) as Button;
            _off.Click += (s, e) =>
                {
                    StopCamera();
                };

            _targetRectangle = GetTemplateChild(_targetRectangleName) as Rectangle;
            _recordingFrame = GetTemplateChild(_recordingFrameName) as Grid;
            _devices = GetTemplateChild(_devicesName) as ComboBox;

            // Check number of devices
            if (DesignerProperties.IsInDesignTool)
            {
                return;
            }

            _availableDevices = CaptureDeviceConfiguration.GetAvailableVideoCaptureDevices();
            if (_availableDevices.Count == 1)
            {
                VisualStateManager.GoToState(this, OneDevice, false);
            }
            else
            {
                VisualStateManager.GoToState(this, MultipleDevices, false);
            }
            
            _devices.ItemsSource = _availableDevices;
            _devices.DisplayMemberPath = "FriendlyName";
        }

        #region Properties

        public int VideoWidth
        {
            get { return (int)GetValue(VideoWidthProperty); }
            set { SetValue(VideoWidthProperty, value); }
        }

        public static readonly DependencyProperty VideoWidthProperty =
            DependencyProperty.Register("VideoWidth", typeof(int), typeof(Webcam), new PropertyMetadata(640));

        public int VideoHeight
        {
            get { return (int)GetValue(VideoHeightProperty); }
            set { SetValue(VideoHeightProperty, value); }
        }

        public static readonly DependencyProperty VideoHeightProperty =
            DependencyProperty.Register("VideoHeight", typeof(int), typeof(Webcam), new PropertyMetadata(480));

        //public bool Mirrored
        //{
        //    get { return (bool)GetValue(MirroredProperty); }
        //    set { SetValue(MirroredProperty, value); }
        //}

        //public static readonly DependencyProperty MirroredProperty =
        //    DependencyProperty.Register("Mirrored", typeof(bool), typeof(Webcam), new PropertyMetadata(true));

        public float FramesPerSecond
        {
            get { return (float)GetValue(FramesPerSecondProperty); }
            set { SetValue(FramesPerSecondProperty, value); }
        }

        public static readonly DependencyProperty FramesPerSecondProperty =
            DependencyProperty.Register("FramesPerSecond", typeof(float), typeof(Webcam), new PropertyMetadata(12f));

        #endregion

        private void StartCamera()
        {
            if (CaptureDeviceConfiguration.AllowedDeviceAccess ||
                        CaptureDeviceConfiguration.RequestDeviceAccess())
            {
                if (_availableDevices.Count == 1)
                {
                    _videoCaptureDevice = _availableDevices[0];
                }
                else
                {
                    _videoCaptureDevice = _devices.SelectedItem as VideoCaptureDevice;
                    if (_videoCaptureDevice == null)
                    {
                        MessageBox.Show("No Video device selected");
                        return;
                    }
                }

                //CaptureSource cs = new CaptureSource();
                CaptureSource = new CaptureSource();
                CaptureSource.VideoCaptureDevice = _videoCaptureDevice;
                VideoFormat desiredFormat = new VideoFormat(
                    PixelFormatType.Format32bppArgb, 
                    this.VideoWidth, 
                    this.VideoHeight, 
                    this.FramesPerSecond);
                CaptureSource.VideoCaptureDevice.DesiredFormat = desiredFormat;
                //this.CaptureSource = cs;

                OnCapturingStarting();

                try
                {
                    CaptureSource.Start();
                    
                    this.VideoBrush = new VideoBrush();
                    this.VideoBrush.SetSource(CaptureSource);
                    _targetRectangle.Fill = VideoBrush;
                    _targetRectangle.Stretch = Stretch.Uniform;
                    //if (this.Mirrored)
                    //{
                    //    ScaleTransform st = new ScaleTransform();
                    //    st.ScaleX = -1.0;
                    //    st.CenterX = this.ActualWidth / 2.0;
                    //    _targetRectangle.RenderTransform = st; 
                    //}
                    OnCapturingStarted();

                    VisualStateManager.GoToState(this, RecordingStateName, false);
                }
                catch (Exception exc)
                {
                    MessageBox.Show(exc.Message);
                }
            }
        }

        private void StopCamera()
        {
            CaptureSource.CaptureImageAsync();
            CaptureSource.CaptureImageCompleted += (s,e)=>
                {
                    Bitmap = e.Result;
                    VisualStateManager.GoToState(this, NotRecordingStateName, false);
                    if (CaptureSource != null)
                    {
                        CaptureSource.Stop();
                    }
                };

            CaptureSource.CaptureFailed += (s, e) =>
                {
                    VisualStateManager.GoToState(this, NotRecordingStateName, false);
                    if (CaptureSource != null)
                    {
                        CaptureSource.Stop();
                    }
                };
            OnCapturingStopped();
        }

        public VideoBrush VideoBrush 
        {
            get
            {
                return _videoBrush;
            }
            set
            {
                _videoBrush = value;
                OnPropertyChanged("VideoBrush");
            }
        }

        public WriteableBitmap Bitmap
        {
            get { return _bitmap; }
            set 
            { 
                _bitmap = value;
                OnPropertyChanged("Bitmap");
            }
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public event EventHandler CapturingStarted;
        private void OnCapturingStarted()
        {
            if (CapturingStarted != null)
            {
                CapturingStarted(this, EventArgs.Empty);
            }
        }

        public event EventHandler CapturingStarting;
        private void OnCapturingStarting()
        {
            if (CapturingStarting != null)
            {
                CapturingStarting(this, EventArgs.Empty);
            }
        }

        public event EventHandler CapturingStopped;
        private void OnCapturingStopped()
        {
            if (CapturingStopped != null)
            {
                CapturingStopped(this, EventArgs.Empty);
            }
        }
    }
}
